#include <string.h>
#include <exception/NpException.h>
#include <sys/time.h>
#include "Gather.h"

using namespace npexception;

static const unsigned int g_receiveBufSize = 1024 * 1024 * 20;

Gather::Gather()
{
  m_fd = INVALID_SOCK_FD;
  m_data = new char[g_receiveBufSize];
  m_dataPtr = m_data;
}

Gather::~Gather()
{
  reset();
  if (m_data != NULL)
  {
    delete[] m_data;
    m_data = NULL;
  }
}

void Gather::setAddr(const char* addr, int addrLength, int port, sockaddr_in& sin)
{
  sin.sin_family = AF_INET;
  memcpy(&sin.sin_addr.s_addr, addr, addrLength);
  sin.sin_port = htons(port);
}

void Gather::conn(const sockaddr_in& sin)
{
  sockaddr_in* tmp = const_cast<sockaddr_in*>(&sin);
  bool option = (connect(m_fd, reinterpret_cast<sockaddr*>(tmp), sizeof(sockaddr_in)) == INVALID_SOCK_FD);
  throwExceptionIfTrue(option, SOCKET_CONN_ERROR);
}

void Gather::httpSet(const char* addr, int addrLength, int port, bool proxy)
{
  bool option = (m_fd != INVALID_SOCK_FD && close(m_fd) == INVALID_SOCK_FD);
  throwExceptionIfTrue(option, SOCKET_CLOSE_FAILED);

  m_fd = socket(AF_INET, SOCK_STREAM, 0);
  throwExceptionIfTrue(m_fd == INVALID_SOCK_FD, SOCKET_GET_FILE_DESCRIPTION_FAILED);

  sockaddr_in sin;
  if (proxy == true)
  {
    setAddr(addr, addrLength, port, sin);
  }
  else
  {
    hostent* host = gethostbyname(addr);
    throwExceptionIfTrue(host == NULL, SOCKET_GET_HOST_ERROR);

    setAddr(host->h_addr, host->h_length, 80, sin);
  }
  conn(sin);
}

void Gather::httpSend(const char* host, int hostLength, const char* items, int itemsLength)
{
#define STRMEMCPY(BUFFER, DATA, DATALENGTH) \
  memcpy(BUFFER, DATA, DATALENGTH); \
  BUFFER += DATALENGTH; \
  httpRequestLength += DATALENGTH

  static char httpRequestString[1024];
  static int httpRequestLength;
  static char* pointToBePushback;

  pointToBePushback = httpRequestString;
  httpRequestLength = 0;

  STRMEMCPY(pointToBePushback, "GET ", HTTP_GET);
  STRMEMCPY(pointToBePushback, items, itemsLength);
  STRMEMCPY(pointToBePushback, " HTTP/1.1\r\n", HTTP_VERSION);
  STRMEMCPY(pointToBePushback, "HOST: ", HTTP_HOST);
  STRMEMCPY(pointToBePushback, host, hostLength);
  STRMEMCPY(pointToBePushback, "\r\n", HTTP_NEWLINE);
  STRMEMCPY(pointToBePushback, "Accept: */*\r\n", HTTP_ACCEPT_ALL);
  STRMEMCPY(pointToBePushback, "Connection: close\r\n\r\n", HTTP_CONNECTION_CLOSE);

  static int flag;
  flag = send(m_fd, httpRequestString, httpRequestLength, 0);
  throwExceptionIfTrue(flag == INVALID_SOCK_FD, SOCKET_SEND_ERROR);
}

int Gather::httpRecv()
{
  const int HTTP_RECV_BUF_SIZE = 1024 * 100;

  static struct timeval timeOutVal = {60, 0};
  int option = setsockopt(m_fd, SOL_SOCKET, SO_RCVTIMEO, static_cast<void*>(&timeOutVal), sizeof(timeval));
  throwExIf(option == -1, SOCKET_RCV_TIMEOUT);

  int toReturn = 0;
  int length = -1;
  static char buffer[HTTP_RECV_BUF_SIZE];
  memset(buffer, 0, HTTP_RECV_BUF_SIZE);
  while (length != 0)
  {
    length = recv(m_fd, buffer, HTTP_RECV_BUF_SIZE, 0);
    throwExceptionIfTrue(length == INVALID_SOCK_FD, SOCKET_RECV_ERROR);
    memcpy(m_dataPtr, buffer, length);
    m_dataPtr += length;
    toReturn += length;
  }
  return toReturn;
}

Gather* Gather::getInstance()
{
  static Gather* instance = new Gather();
  return instance;
}

void Gather::requestParse(const char *request, char *host, char *items)
{
  throwExceptionIfTrue(request == NULL || host == NULL || items == NULL, REQUEST_STRING_ERROR);
  char* ptr = const_cast<char*>(request) + 7;
  char* hostPtr = ptr;
  for (; ; ++ptr)
  {
    if (*ptr == '/')
    {
      break;
    }
    throwExceptionIfTrue(*ptr == 0, REQUEST_STRING_ERROR);
  }
  int hostLength = ptr - hostPtr;
  memcpy(host, hostPtr, hostLength);
  host[hostLength] = 0;
  memcpy(items, ptr, strlen(request) - hostLength + 1);
}

char* Gather::sendNrecv(const char *proxyIp, int port, const char *request, int& receiveLength)
{
  reset();
  static char* host = new char[1024 * 10];
  static char* items = new char[1024 * 1024];
  requestParse(request, host, items);
  if (proxyIp == NULL)
  {
    httpSet(host, 0, 0, false);
  }
  else
  {
    httpSet(proxyIp, strlen(proxyIp), port, true);
  }
  httpSend(host, strlen(host), items, strlen(items));
  receiveLength = httpRecv();
  return m_data;
}

char* Gather::collect(const char *request) throw (int, const char*)
{
  static int noUse;
  return collectAPic(request, noUse);
}

char* Gather::getPos(const char *beginning, int dataLen)
{
  bool flag = false;
  for (int i = 0; i < dataLen; ++i, ++beginning)
  {
    if (*beginning == '\r' && memcmp(beginning, "\r\n\r\n", 4) == 0)
    {
      beginning += 4;
      flag = true;
      break;
    }
  }
  return (flag == true) ? const_cast<char*>(beginning) : NULL;
}

char* Gather::collectAPic(const char *request, int &len)
{
  char* reply = collect(request, len);
  char* pic = getPos(reply, len);
  throwExIf(pic == NULL, SOCKET_COLLECT_PIC_ERROR);
  len -= (pic - reply);
  return pic;
}

char* Gather::collect(const char *request, int& receiveLength) throw (int, const char*)
{
    printf("link:%s\n",request);
  return sendNrecv(NULL, 0, request, receiveLength);
}

char* Gather::collectByProxy(const char *proxyIp, int port, const char *request, int& receiveLength) throw (int, const char*)
{
  return sendNrecv(proxyIp, port, request, receiveLength);
}

void Gather::reset()
{
  bool option = (m_fd !=INVALID_SOCK_FD && close(m_fd) == INVALID_SOCK_FD);
  throwExceptionIfTrue(option, SOCKET_CLOSE_FAILED);
  m_fd = INVALID_SOCK_FD;
  m_dataPtr = m_data;
  memset(m_data, 0, g_receiveBufSize);
}
